package online.heycm.platform.redis.aop;

import lombok.extern.slf4j.Slf4j;
import online.heycm.platform.common.entity.exception.Assert;
import online.heycm.platform.redis.RedisCache;
import online.heycm.platform.redis.annotation.Idempotent;
import online.heycm.platform.redis.annotation.IdempotentKey;
import online.heycm.platform.redis.exception.IdempotentException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.MessageFormat;

/**
 * 幂等切面
 *
 * @author heycm
 * @since 2023/12/7 19:19
 */
@Slf4j
@Aspect
public class IdempotentAop {

    private static final String LOCK_KEY = "idempotent:lock:{0}:{1}";

    private final RedisCache redisCache;

    public IdempotentAop(RedisCache redisCache) {
        this.redisCache = redisCache;
    }

    @Pointcut("@annotation(online.heycm.platform.redis.annotation.Idempotent)")
    private void cut() {}

    @Around("cut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        final String key = getLockKey(idempotent, method, pjp.getArgs());
        try {
            idempotent(key, idempotent.expire(), idempotent.errorMsg());
            return pjp.proceed();
        } finally {
            release(key);
        }
    }

    private void idempotent(String key, long expire, String errorMsg) {
        boolean ok = redisCache.setNx(key, "1", expire);
        Assert.isTrue(ok, new IdempotentException(errorMsg));
    }

    private void release(String key) {
        redisCache.del(key);
    }


    private String getLockKey(Idempotent idempotent, Method method, Object[] args) throws IllegalAccessException {
        String key = getIdempotentKey(method, args);
        return MessageFormat.format(LOCK_KEY, idempotent.prefix(), key);
    }

    private String getIdempotentKey(Method method, Object[] args) throws IllegalAccessException {
        // 获取目标方法参数上@IdempotentKey注解，将其值作为key
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                if (annotation instanceof IdempotentKey) {
                    return args[i].toString();
                }
            }
        }
        // 获取目标方法参数属性上@IdempotentKey注解，将其值作为key
        for (Object arg : args) {
            for (Field field : arg.getClass().getDeclaredFields()) {
                IdempotentKey annotation = field.getAnnotation(IdempotentKey.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    return field.get(arg).toString();
                }
            }
        }
        throw new IllegalArgumentException("没有找到@IdempotentKey注解");
    }

}
